/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */
package org.bhf.providers.security.authentication;

import org.bhf.security.common.LoginID;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Tracks virtual sessions as a helper for determining N-stries and disable, login failure
 * notification, etc. A session starts as soon as security becomes away of a login attempt
 * (regardless of outcome.) A session ends when the session time out has been reach or on
 * an explicit logout. A session is renewed on a succesful login attempt.
 * @exclude
 */
final class SessionTracker
{
    private final Map<LoginID,Session>  sessions = new HashMap<LoginID,Session>(); // LoginID to Session

    /**
     * Get the <code>Session</code> for the given login ID, creating one if necessary.
     * @param loginID Must not be <code>null</code>.
     * @param sessionTimeOut Session time out in ms
     * @return The <code>Session</code> for the given login ID, creating one if necessary.
     */
    Session getSession( final LoginID loginID, final long sessionTimeOut )
    {
        reap();

        synchronized( sessions )
        {
            Session session = sessions.get( loginID );
            if( session == null )
            {
                session = new Session( sessionTimeOut, loginID );
                sessions.put( loginID, session );
            }
            return session;
        }
    }

    /**
     * Represents a virtual session for a specific login ID
     */
    public final class Session
    {
        private final long    sessionTimeOut;
        private final LoginID loginID;
        private       long    expiresOn;
        private       int     numberOfFailures;
        private       int     numberOfSucesses;

        private Session( final long sessionTimeOut, final LoginID loginID )
        {
            this.sessionTimeOut = sessionTimeOut;
            this.loginID        = loginID;
            this.expiresOn      = System.currentTimeMillis() + sessionTimeOut;
        }

        /**
         * Log a login success.
         * @return the number of successful logins for this session since the session started
         *      or the last failed attempt.
         */
        public synchronized int        logSuccess()
        {
            numberOfFailures = 0;
            expiresOn = System.currentTimeMillis() + sessionTimeOut;
            reap();
            numberOfSucesses++;
            return numberOfSucesses;
        }

        /**
         * Cancel this session.
         */
        public synchronized void       logOut()
        {
            synchronized( sessions )
            {
                sessions.remove( loginID );
            }
        }

        /**
         * Log a failed login attempt.
         * @return The number of failures, including this one, since the last successful login.
         */
        public synchronized int        logFailure()
        {
            numberOfSucesses = 0;
            numberOfFailures++;
            return numberOfFailures;
        }

        /**
         * Get the number of failures since the last successful login.
         * @return The number of failures since the last successful login.
         */
        public synchronized int        getNumberOfFailures()
        {
            return numberOfFailures;
        }

        /**
         * Return <code>true</code> iff this session is expired wrt to the given current time.
         * @param currentTime Current time in ms (as in System.currentTimeMillis()).
         * @return <code>true</code> iff this session is expired wrt to the given current time.
         */
        public synchronized boolean    isExpired( final long currentTime )
        {
            return currentTime > expiresOn;
        }
    }

    //
    // Private
    //

    private void reap()
    {
        final long currentTime = System.currentTimeMillis();

        synchronized( sessions )
        {
            for( Iterator i = sessions.values().iterator(); i.hasNext(); )
            {
                final Session session = (Session)i.next();
                if( session.isExpired( currentTime ) )
                    i.remove();
            }
        }
    }
}


